﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Loader;


namespace FI.BatchJob.Web.Helper
{
    public class DllLoader
    {
        private Dictionary<string, CollectibleAssemblyLoadContext> _dicAssemblyContext
             = new Dictionary<string, CollectibleAssemblyLoadContext>();

        public readonly string DLL_PATH = Path.Combine(Directory.GetCurrentDirectory(), "jobdllfile");

        private FileSystemWatcher _watcher;

        public static DllLoader Instance = new DllLoader();

        private DllLoader()
        {
            Action<string> onFileChanged = path =>
            {
                if (Path.GetExtension(path).ToLower() == ".dll")
                {
                    foreach (var assemblyFile in _dicAssemblyContext.Keys)
                    {
                        LoadDll(assemblyFile);
                    }
                }
            };
            _watcher = new FileSystemWatcher();

            _watcher.Path = DLL_PATH;

            _watcher.IncludeSubdirectories = true;
            _watcher.NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.FileName;
            _watcher.Changed += (sender, e) => onFileChanged(e.FullPath);
            _watcher.Created += (sender, e) => onFileChanged(e.FullPath);
            _watcher.Deleted += (sender, e) => onFileChanged(e.FullPath);
            _watcher.Renamed += (sender, e) => { onFileChanged(e.FullPath); onFileChanged(e.OldFullPath); };
            _watcher.EnableRaisingEvents = true;


        }
        [MethodImpl(MethodImplOptions.NoInlining)]
        public Assembly LoadDll(string assemblyFile)
        {
            UnloadDll(assemblyFile);

            CollectibleAssemblyLoadContext context = new CollectibleAssemblyLoadContext();

            _dicAssemblyContext.Add(assemblyFile, context);

            var assemblyPath = Path.Combine(DLL_PATH, assemblyFile);

            using (var fs = new FileStream(assemblyPath, FileMode.Open, FileAccess.Read))
            {
                return context.LoadFromStream(fs);
            }

        }
        [MethodImpl(MethodImplOptions.NoInlining)]
        public void UnloadDll(string assemblyFile)
        {
            var context = _dicAssemblyContext[assemblyFile];

            if (context != null)
            {
                context.Unload();

                _dicAssemblyContext.Remove(assemblyFile);
            }
        }
    }

    public class CollectibleAssemblyLoadContext : AssemblyLoadContext
    {
        public CollectibleAssemblyLoadContext() : base(isCollectible: true)
        { }

        protected override Assembly Load(AssemblyName assemblyName)
        {
            return null;
        }
    }
}
